﻿using System;
using System.Windows;
using System.Windows.Controls;

namespace theeg.DependencyAnalysis.CommonAddin.Views
{
    public class LateRenderStackPanel : StackPanel
    {
        private double _lastEstimatedOffset;

        public ScrollViewer GoverningScrollViewer
        {
            get { return (ScrollViewer)GetValue(GoverningScrollViewerProperty); }
            set { SetValue(GoverningScrollViewerProperty, value); }
        }

        public static readonly DependencyProperty GoverningScrollViewerProperty =
            DependencyProperty.Register("GoverningScrollViewer", typeof(ScrollViewer), typeof(LateRenderStackPanel), new PropertyMetadata());

        public static readonly DependencyProperty AlreadyLoadedProperty =
            DependencyProperty.RegisterAttached("AlreadyLoaded", typeof(bool), typeof(LateRenderStackPanel), new PropertyMetadata());

        public static bool GetAlreadyLoaded(DependencyObject target)
        {
            return (bool)target.GetValue(AlreadyLoadedProperty);
        }

        public static void SetAlreadyLoaded(DependencyObject target, bool value)
        {
            target.SetValue(AlreadyLoadedProperty, value);
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == GoverningScrollViewerProperty)
            {
                var oldScrollViewer = e.OldValue as ScrollViewer;
                if (oldScrollViewer != null)
                {
                    oldScrollViewer.ScrollChanged -= OnScrollChanged;
                    oldScrollViewer.SizeChanged -= OnSizeChanged;
                }
                var newScrollViewer = e.NewValue as ScrollViewer;
                if (newScrollViewer != null)
                {
                    newScrollViewer.ScrollChanged += OnScrollChanged;
                    newScrollViewer.SizeChanged += OnSizeChanged;
                }
            }
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            InvalidateMeasure();
        }

        private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (e.VerticalOffset > _lastEstimatedOffset)
            {
                InvalidateMeasure();
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            var scrollViewer = GoverningScrollViewer;

            var elementAvailableSize = new Size(availableSize.Width, double.PositiveInfinity);
            double visibleExtent = scrollViewer == null ? double.PositiveInfinity : (scrollViewer.ActualHeight + scrollViewer.VerticalOffset) + 100;

            double currentExtent = 0;
            double maxWidth = 0;
            var currentIndex = 0;

            foreach (UIElement child in InternalChildren)
            {
                var alreadyLoaded = GetAlreadyLoaded(child);
                if (currentExtent > visibleExtent && !alreadyLoaded)
                {
                    child.Visibility = Visibility.Collapsed;
                }
                else
                {
                    currentIndex++;
                    if (!alreadyLoaded)
                    {
                        child.Visibility = Visibility.Visible;
                        SetAlreadyLoaded(child, true);
                    }

                    child.Measure(elementAvailableSize);

                    var childDesired = child.DesiredSize;
                    currentExtent += childDesired.Height;
                    maxWidth = Math.Max(maxWidth, childDesired.Width);
                }
            }

            _lastEstimatedOffset = scrollViewer == null ? double.PositiveInfinity : currentExtent - 100 - scrollViewer.ActualHeight;
            var count = InternalChildren.Count;
            if (count > currentIndex)
            {
                currentExtent += (currentExtent / currentIndex) * (count - currentIndex);
            }

            return new Size(maxWidth, currentExtent);
        }
    }
}
